/*!
 * \file WtSimpRiskMon.cpp
 * \project	WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief 简单风控监控器类实现文件
 *		  本文件实现了WtSimpleRiskMon简单风控监控器类的所有方法
 *		  提供基于回撤控制的实时风险监控功能
 *		  支持日内和多日回撤监控，动态仓位控制
 *		  采用独立线程运行，确保风控监控的实时性和可靠性
 */
#include "WtSimpRiskMon.h"

#include "../Includes/WTSRiskDef.hpp"
#include "../Includes/WTSVariant.hpp"
#include "../Share/TimeUtils.hpp"
#include "../Share/decimal.h"
#include "../Share/fmtlib.h"

extern const char* FACT_NAME;

/**
 * @brief 获取监控器名称
 * @return 返回监控器名称字符串"WtSimpleRiskMon"
 * @details 返回当前风控监控器的标识名称
 *          用于监控器识别、日志记录和调试
 */
const char* WtSimpleRiskMon::getName()
{
	return "WtSimpleRiskMon";
}

/**
 * @brief 获取工厂名称
 * @return 返回所属工厂名称字符串
 * @details 返回创建此监控器的工厂名称
 *          用于工厂验证和监控器管理
 */
const char* WtSimpleRiskMon::getFactName()
{
	return FACT_NAME;
}

/**
 * @brief 初始化风控监控器
 * @param ctx 组合上下文指针
 * @param cfg 配置参数
 * @details 根据配置参数初始化风控监控器，加载以下参数：
 *          - calc_span: 计算时间间隔（秒）
 *          - risk_span: 回撤比较时间窗口（分钟）
 *          - basic_ratio: 基础盈利比例，触发风控保护的盈利阈值
 *          - inner_day_fd: 日内最高点回撤阈值
 *          - inner_day_active: 日内风控开关
 *          - multi_day_fd: 多日最高点回撤阈值
 *          - multi_day_active: 多日风控开关
 *          - base_amount: 基础资金金额
 *          - risk_scale: 风控仓位控制系数
 *          输出初始化日志，记录关键参数信息
 */
void WtSimpleRiskMon::init(WtPortContext* ctx, WTSVariant* cfg)
{
	WtRiskMonitor::init(ctx, cfg);

	_calc_span = cfg->getUInt32("calc_span");
	_risk_span = cfg->getUInt32("risk_span");
	_basic_ratio = cfg->getUInt32("basic_ratio");
	_inner_day_fd = cfg->getDouble("inner_day_fd");
	_inner_day_active = cfg->getBoolean("inner_day_active");
	_multi_day_fd = cfg->getDouble("multi_day_fd");
	_multi_day_active = cfg->getBoolean("multi_day_active");
	_base_amount = cfg->getDouble("base_amount");
	_risk_scale = cfg->getDouble("risk_scale");

	// 输出初始化参数日志（中文版本已注释）
	//ctx->writeRiskLog(fmt::sprintf("参数初始化完成, 检查时间间隔: %u 秒, 日内回撤: %s(%.2f%%), 多日回撤: %s(%.2f%%), 资金基数: %.1f, 盈利边界: %.2f%%, 回撤时间: %u mins, 风控仓位控制系数: %.2f",
	//	_calc_span, _inner_day_active ? "开启" : "关闭", _inner_day_fd, _multi_day_active ? "开启" : "关闭", _multi_day_fd, _base_amount, _basic_ratio, _risk_span, _risk_scale);

	// 输出初始化参数日志（英文版本）
	ctx->writeRiskLog(fmt::sprintf("Params inited, Checking frequency: %u s, MaxIDD: %s(%.2f%%), MaxMDD: %s(%.2f%%), Capital: %.1f, Profit Boudary: %.2f%%, Calc Span: %u mins, Risk Scale: %.2f",
		_calc_span, _inner_day_active ? "ON" : "OFF", _inner_day_fd, _multi_day_active ? "ON" : "OFF", _multi_day_fd, _base_amount, _basic_ratio, _risk_span, _risk_scale).c_str());
}

/**
 * @brief 启动风控监控
 * @details 启动独立监控线程，开始实时风险监控
 *          监控线程执行以下逻辑：
 *          1. 检查交易状态，只在交易时间内进行风控监控
 *          2. 获取组合资金信息，计算当前盈亏状况
 *          3. 执行日内回撤监控：
 *             - 检查是否启用日内风控
 *             - 计算从最高点的回撤比例
 *             - 当盈利超过基础比例且回撤超过阈值时触发风控
 *             - 风控措施：降低仓位到指定比例
 *          4. 执行多日回撤监控：
 *             - 检查是否启用多日风控
 *             - 计算从历史最高点的回撤比例
 *             - 当回撤超过阈值时触发风控
 *             - 风控措施：清空所有仓位
 *          5. 按指定间隔重复检查
 *          
 *          风控策略详解：
 *          策略1: 当组合的盈利超过基础盈利比例时，启动回撤保护
 *                动态权益 ÷ 昨日结算时的动态权益 > 101%
 *          策略2: 30min内，从最高点回撤 80%以上
 *                30min内，从组合最高点回撤 80%以上
 *          
 *          风控措施:
 *          方式A: 降低品种仓位到 30% 仓位，等待下一个交易信号重新开仓
 *          方式B: 平掉盈利品种仓位，等待下一个交易信号重新开仓
 */
void WtSimpleRiskMon::run()
{
	if (_thrd)
		return;

	_thrd.reset(new std::thread([this](){
		while (!_stopped)
		{
			if (_ctx && _ctx->isInTrading())
			{
				WTSPortFundInfo* fundInfo = _ctx->getFundInfo();
				const WTSFundStruct& fs = fundInfo->fundInfo();
				/*
				* 策略1: 当组合的盈利超过基础盈利比例时，启动回撤保护
				*		动态权益 ÷ 昨日结算时的动态权益 > 101%
				* 策略2: 30min内，从最高点回撤 80%以上
				*		30min内，从组合最高点回撤 80%以上
				*
				* 风控措施: 
				* 方式A:  降低品种仓位到 30% 仓位，等待下一个交易信号重新开仓
				* 方式B:  平掉盈利品种仓位，等待下一个交易信号重新开仓
				*/

				// 日内回撤监控
				if (_inner_day_active && fs._max_dyn_bal != DBL_MAX)
				{
					double predynbal = fundInfo->predynbalance() + _base_amount;	// 昨日动态权益
					double maxBal = fs._max_dyn_bal + _base_amount;					// 最高动态权益
					double curBal = fs._balance + fs._dynprofit + _base_amount;		// 当前动态权益

					double rate = 0.0;
					if(!decimal::eq(maxBal, predynbal))
						rate = (maxBal - curBal) * 100 / (maxBal - predynbal);	// 计算盈利回撤比例

					// 检查当前动态权益是否超过止盈边界
					if (maxBal > (_basic_ratio*predynbal / 100.0))
					{
						
						/*
						 *	这里要转换成日内分钟数
						 *	因为这里的时间戳是带日期的时间戳, 
						 *	可能会因为跨日信息时差问题, 导致计算错误
						 *	所以这里统一转换成日内分钟数
						 */
						uint32_t maxTime = _ctx->transTimeToMin(fundInfo->max_dynbal_time());	
						uint32_t curTime = _ctx->transTimeToMin(_ctx->getCurTime());			// 转换成日内分钟数

						if (rate >= _inner_day_fd && curTime - maxTime <= _risk_span && !_limited)
						{
							_ctx->writeRiskLog(fmt::sprintf("Current IDD %.2f%%, 超过MaxIDD %.2f%%, Position down to %.1f%%", rate, _inner_day_fd, _risk_scale).c_str());
							_ctx->setVolScale(_risk_scale);
							_limited = true;
						}
						else
						{
							_ctx->writeRiskLog(fmt::sprintf("Current PR: %.2f%%, Current IDD: %.2f%%", curBal*100.0 / predynbal, rate).c_str());
							//_limited = false;
						}
					}
					else
					{
						// 当前组合动态权益没有超过盈利边界
						_ctx->writeRiskLog(fmt::sprintf("Current PR: %.2f%%", curBal*100.0 / predynbal).c_str());
						//_limited = false;
					}
				}

				// 多日回撤监控
				if (_multi_day_active && fs._max_md_dyn_bal._date != 0)
				{
					double maxBal = fs._max_md_dyn_bal._dyn_balance + _base_amount;
					double curBal = fs._balance + fs._dynprofit + _base_amount;

					if (curBal < maxBal)
					{
						double rate = (maxBal - curBal) * 100 / maxBal;
						if (rate >= _multi_day_fd)
						{
							_ctx->writeRiskLog(fmt::sprintf("Current MDD %.2f%%, 超过MaxMDD %.2f%%, Position down to 0.0%%", rate, _multi_day_fd).c_str());
							_ctx->setVolScale(0.0);
						}
					}
				}
			}

			_last_time = TimeUtils::getLocalTimeNow();

			// 等待下一次检查
			while (!_stopped)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(2));
				uint64_t now = TimeUtils::getLocalTimeNow();;
				if(now - _last_time >= _calc_span*1000)
					break;
			}
		}
	}));
}

/**
 * @brief 停止风控监控
 * @details 停止监控线程，结束风险监控
 *          设置停止标志为true，等待监控线程安全退出
 *          确保线程资源正确释放
 */
void WtSimpleRiskMon::stop()
{
	_stopped = true;
	if (_thrd)
		_thrd->join();
}